﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Processing.Structures;
using RayTracer.Mathematics;
using System.Xml;

namespace RayTracer.Structures
{
    /// <summary>
    /// Modelliert einen Punkt.
    /// </summary>
    public struct Point
    {
        #region Attribute
        /// <summary>
        /// Gibt und setzt die X-Koordinate.
        /// </summary>
        public float X;

        /// <summary>
        /// Gibt und setzt die Y-Koordinate.
        /// </summary>
        public float Y;

        /// <summary>
        /// Gibt und setzt die Z-Koordinate.
        /// </summary>
        public float Z;
        #endregion Attribute

        #region Methoden
        #region Konstruktoren
        /// <summary>
        /// Initialisiert die Struktur.
        /// </summary>
        /// <param name="x">Der X-Wert.</param>
        /// <param name="y">Der Y-Wert.</param>
        /// <param name="z">Der Z-Wert.</param>
        public Point(float x, float y, float z)
        {
            X = x;
            Y = y;
            Z = z;
        }
        #endregion Konstruktoren

        /// <summary>
        /// Rotiert den Punkt um eine Achse.
        /// </summary>
        /// <param name="axis">Die Achse.</param>
        /// <param name="degree">Die Gradzahl.</param>
        public void Rotate(Vector axis, float degree)
        {
            Point result = GetRotated(axis, degree);
            X = result.X;
            Y = result.Y;
            Z = result.Z;
        }

        /// <summary>
        /// Gibt den um eine Achse rotierten Punkt zurück.
        /// </summary>
        /// <param name="axis">Die Achse.</param>
        /// <param name="degree">Die Gradzahl.</param>
        public Point GetRotated(Vector axis, float degree)
        {
            /*
             *  r' = r cos p + (1 - cos p)(n*r)n + (nxr)sin p
             *
             *  n = normierter Vektor (Drehachse)
             *  p = Drehwinkel
             *  r = Vektor, der gedreht werden soll
             *  r' = Bildvektor
             * */
            Vector n = axis.Normalized;
            Vector r = this - new Point(0,0,0);
            return (r * (float)Math.Cos(degree.ToRAD())) +
                (n * ((n * r) * (1.0f - (float)Math.Cos(degree.ToRAD())))) +
                (n / r) * (float)Math.Sin(degree.ToRAD()) - new Point(0, 0, 0);
        }

        #region Operatoren
        /// <summary>
        /// Addiert zu einem Punkt einen Vektor.
        /// </summary>
        /// <param name="A">Der Punkt.</param>
        /// <param name="B">Der Vektor.</param>
        /// <returns>Der neue Punkt.</returns>
        public static Point operator +(Point A, Vector B)
        {
            return new Point(A.X + B.X, A.Y + B.Y, A.Z + B.Z);
        }

        /// <summary>
        /// Addiert zu einem Punkt einen Punkt.
        /// </summary>
        /// <param name="A">Der Punkt.</param>
        /// <param name="B">Der andere Punkt.</param>
        /// <returns>Der neue Vektor.</returns>
        public static Vector operator +(Point A, Point B)
        {
            return new Vector(A.X + B.X, A.Y + B.Y, A.Z + B.Z);
        }

        /// <summary>
        /// Subtrahiert von einem Punkt einen Vektor.
        /// </summary>
        /// <param name="A">Der Punkt.</param>
        /// <param name="B">Der Vektor.</param>
        /// <returns>Der neue Punkt.</returns>
        public static Point operator -(Point A, Vector B)
        {
            return new Point(A.X - B.X, A.Y - B.Y, A.Z - B.Z);
        }

        /// <summary>
        /// Subtrahiert von einem Punkt einen Punkt.
        /// </summary>
        /// <param name="A">Der Punkt.</param>
        /// <param name="B">Der andere Punkt.</param>
        /// <returns>Der neue Vektor.</returns>
        public static Vector operator -(Point A, Point B)
        {
            return new Vector(A.X - B.X, A.Y - B.Y, A.Z - B.Z);
        }

        #region Boolsche
        /// <summary>
        /// Prüft ob ein Punkt gleich einem anderen ist.
        /// </summary>
        /// <param name="A">Der Punkt.</param>
        /// <param name="B">Der andere Punkt.</param>
        /// <returns>Der Indikator ob die beiden Punkte gleich sind.</returns>
        public static bool operator ==(Point A, Point B)
        {
            return A.X == B.X && A.Y == B.Y && A.Z == B.Z;
        }

        /// <summary>
        /// Prüft ob ein Punkt ungleich einem anderen ist.
        /// </summary>
        /// <param name="A">Der Punkt.</param>
        /// <param name="B">Der andere Punkt.</param>
        /// <returns>Der Indikator ob die beiden Punkte ungleich sind.</returns>
        public static bool operator !=(Point A, Point B)
        {
            return !(A == B);
        }

        /// <summary>
        /// Gibt an ob das Objekt gleich einem anderen ist.
        /// </summary>
        /// <param name="obj">Das andere Objekt.</param>
        /// <returns>Der Indikator.</returns>
        public override bool Equals(object obj)
        {
            return base.Equals(obj) && (obj is Point ? this == (Point)obj : false);
        }

        /// <summary>
        /// Gibt den Hashcode des Objekts zurück.
        /// </summary>
        /// <returns>Der Hashcode.</returns>
        public override int GetHashCode()
        {
            return ((int)X ^ (int)Y ^ (int)Z);
        }
        #endregion Boolsche
        #endregion Operatoren
        #region Typenumwandlungen
        /// <summary>
        /// Castet den Punkt in einen Vektor.
        /// </summary>
        /// <param name="p">Der Punkt.</param>
        /// <returns>Der Vektor.</returns>
        public static explicit operator Vector(Point p)
        {
            return new Vector(p.X, p.Y, p.Z);
        }

        /// <summary>
        /// Gibt den Punkt als Float4-Array zurück.
        /// </summary>
        /// <returns>Der Float4.</returns>
        public Float4 Serialize() { return new Float4(X, Y, Z, 0); }

        /// <summary>
        /// Erstellt eine Kopie des Punkts.
        /// </summary>
        /// <returns>Die Kopie.</returns>
        public Point Clone() { return new Point(X, Y, Z); }

        /// <summary>
        /// Gibt eine Zeichenkettenrepräsentation der Struktur zurück.
        /// </summary>
        /// <returns>Die Zeichenkette.</returns>
        public override string ToString()
        {
            return "x: " + X.ToString() + " y: " + Y.ToString() + " z: " + Z.ToString();
        }

        /// <summary>
        /// Erstellt einen Punkt aus einem XML-Knoten.
        /// </summary>
        /// <param name="xmlNode">Der XML-Knoten.</param>
        /// <returns>Der Punkt.</returns>
        internal static Point FromXml(XmlNode xmlNode)
        {
            XmlNode nodeX = xmlNode.Attributes.GetNamedItem("x");
            XmlNode nodeY = xmlNode.Attributes.GetNamedItem("y");
            XmlNode nodeZ = xmlNode.Attributes.GetNamedItem("z");
            Point p = new Point();
            if (nodeX != null) float.TryParse(nodeX.InnerText, out p.X);
            if (nodeY != null) float.TryParse(nodeY.InnerText, out p.Y);
            if (nodeZ != null) float.TryParse(nodeZ.InnerText, out p.Z);
            return p;
        }

        /// <summary>
        /// Wandelt den Punkt in ein XML-Knoten um.
        /// </summary>
        /// <param name="xmlDocument">Das zugrunde liegende XML-Dokument.</param>
        /// <param name="name">Der Name des Vektors.</param>
        /// <returns>Der XML-Knoten.</returns>
        internal XmlNode ToXml(XmlDocument xmlDocument, string name)
        {
            XmlNode node = xmlDocument.CreateNode(XmlNodeType.Element, name, null);
            XmlAttribute attributeX = xmlDocument.CreateAttribute("x");
            attributeX.InnerText = X.ToString();
            XmlAttribute attributeY = xmlDocument.CreateAttribute("y");
            attributeY.InnerText = Y.ToString();
            XmlAttribute attributeZ = xmlDocument.CreateAttribute("z");
            attributeZ.InnerText = Z.ToString();
            node.Attributes.Append(attributeX);
            node.Attributes.Append(attributeY);
            node.Attributes.Append(attributeZ);
            return node;
        }
        #endregion Typenumwandlungen
        #endregion Methoden
    }
}
